export const getUtf8Bytes = (str: string): number[] => {
	const bytes: number[] = []
	for (let i = 0, j = 0; i < str.length; i++) {
		const c = str.charCodeAt(i)
		if (c <= 0x7F) {
			// One byte sequence
			bytes[j++] = c
		} else if (c <= 0x7FF) {
			// Two byte sequence
			bytes[j++] = 0xC0 | ((c & 0x7C0) >>> 6)
			bytes[j++] = 0x80 | (c & 0x3F)
		} else if (c <= 0xD7FF || (c >= 0xE000 && c <= 0xFFFF)) {
			// Three byte sequence，Source character is not a UTF-16 surrogate
			bytes[j++] = 0xE0 | ((c & 0xF000) >>> 12)
			bytes[j++] = 0x80 | ((c & 0x0FC0) >>> 6)
			bytes[j++] = 0x80 | (c & 0x3F)
		} else {
			// Four byte sequence
			if (i === length - 1) {
				throw new Error('Unpaired surrogate ' + str[i] + ' (index ' + i + ')')
			}

			// Retrieve surrogate
			const d = str.charCodeAt(++i)
			if (c < 0xD800 || c > 0xDBFF || d < 0xDC00 || d > 0xDFFF) {
				throw new Error('Unpaired surrogate ' + str[i] + ' (index ' + i + ')')
			}

			const cp = ((c & 0x03FF) << 10) + (d & 0x03FF) + 0x10000

			bytes[j++] = 0xF0 | ((cp & 0x1C0000) >>> 18)
			bytes[j++] = 0x80 | ((cp & 0x03F000) >>> 12)
			bytes[j++] = 0x80 | ((cp & 0x000FC0) >>> 6)
			bytes[j++] = 0x80 | (cp & 0x3F)
		}
	}
	return bytes
}

export const getUtf8String = (bytes: number[] | null): string => {
	if (bytes == null || bytes.length === 0) return ''
	// Holds decoded characters
	const codePoints: number[] = []
	// Index into codepoints
	let c = 0
	let i = 0
	while (i < bytes.length) {
		const b1 = bytes[i]
		if (b1 < 128) {
			// One byte sequence
			codePoints[c++] = b1
			i++
		} else if (b1 < 194) {
			throw new Error('Unexpected continuation byte')
		} else if (b1 < 224) {
			// Two byte sequence
			const b2 = bytes[i + 1]
			if (b2 < 128 || b2 > 191) {
				throw new Error('Bad continuation byte')
			}
			codePoints[c++] = ((b1 & 0x1F) << 6) + (b2 & 0x3F)
			i += 2
		} else if (b1 < 240) {
			// Three byte sequence
			const b2 = bytes[i + 1]
			if (b2 < 128 || b2 > 191) {
				throw new Error('Bad continuation byte')
			}

			const b3 = bytes[i + 2]
			if (b3 < 128 || b3 > 191) {
				throw new Error('Bad continuation byte')
			}

			codePoints[c++] = ((b1 & 0x0F) << 12) + ((b2 & 0x3F) << 6) + (b3 & 0x3F)
			i += 3
		} else if (b1 < 245) {
			// Four byte sequence
			const b2 = bytes[i + 1]
			if (b2 < 128 || b2 > 191) {
				throw new Error('Bad continuation byte')
			}

			const b3 = bytes[i + 2]
			if (b3 < 128 || b3 > 191) {
				throw new Error('Bad continuation byte')
			}

			const b4 = bytes[i + 3]
			if (b4 < 128 || b4 > 191) {
				throw new Error('Bad continuation byte')
			}

			let cp = ((b1 & 0x07) << 18) + ((b2 & 0x3F) << 12) + ((b3 & 0x3F) << 6) + (b4 & 0x3F)
			cp -= 0x10000

			// Turn code point into two surrogate pairs
			codePoints[c++] = 0xD800 + ((cp & 0x0FFC00) >>> 10)
			codePoints[c++] = 0xDC00 + (cp & 0x0003FF)
			i += 4
		} else {
			throw new Error('Illegal byte')
		}
	}

	// Browsers may have hardcoded or implicit limits on the array length when applying a function
	// See: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/apply//apply_and_built-in_functions
	const limit = 1 << 16
	const length = codePoints.length
	if (length < limit) {
		return String.fromCharCode.apply(String, codePoints)
	}
	const chars: string[] = []
	for (i = 0; i < length; i += limit) {
		chars.push(String.fromCharCode.apply(String, codePoints.slice(i, i + limit)))
	}
	return chars.join('')
}
